class SerializeError extends Error {
  constructor(err) {
    super(err instanceof Error ? err.message : err);
    this.name = 'SerializeError';
  }
}

/**
 * 可以理解为特殊的 JSON.stringify,会对key做排序
 * @throws {SerializeError}
 */
function serialize(params: unknown): string {
  try {
    return _serialize(params, new WeakSet());
  } catch (err) {
    throw new SerializeError(err);
  }
}

function _serialize(params: any, serializedCache: WeakSet<any>): string {
  if (typeof params === 'function' || typeof params === 'symbol') {
    throw new Error('序列化参数失败: 含有function或symbol');
  }
  if (typeof params !== 'object' || params === null) {
    return JSON.stringify(params);
  }
  if (serializedCache.has(params)) {
    throw new Error('序列化参数失败: 含有循环引用');
  }
  serializedCache.add(params);

  if (Array.isArray(params)) {
    const res = params.map((p) => _serialize(p, serializedCache)).join(',');
    serializedCache.delete(params);
    return res;
  }
  if (Object.getOwnPropertySymbols(params).length) {
    throw new Error('序列化参数失败: key含有symbol');
  }
  const _ks = Object.keys(params).sort();
  let res = '';
  for (const _k of _ks) {
    const _k_str = _serialize(_k, serializedCache);
    res = res + `${_k_str}:${_serialize(params[_k], serializedCache)},`;
  }
  res = '{' + res.substring(0, res.length - 1) + '}';
  serializedCache.delete(params);
  return res;
}

type LoggerFn = (info: {
  /** 得到的返回结果 */
  res: unknown;
  /** 是否真正执行 */
  doCall: boolean;
  /** 传入参数 */
  params: any[];
  /** 序列化传入参数的结果 */
  key?: string;
  /** 虽然成功执行，但有不合理的因素-信息 */
  err?: Error;
}) => void;

/**
 * 包装一个无状态函数，返回新函数。新的函数瞬间多次的【相同调用】会合并成一个，然后返回相同结果。
 ** 判断【相同调用】的依据是参数内容是否相同，但如果参数有function或symbol类型，总是会被认为不同的
 */
export default function unify<T extends Function>(fn: T, loggerFn?: LoggerFn): T {
  if (typeof fn !== 'function') {
    throw new Error('需要传入函数类型');
  }

  const map: {
    [key: string]: {
      resolves: Array<(value: T) => void>;
      rejects: Array<(err: Error) => void>;
      requesting: boolean;
    };
  } = {};
  const logger: LoggerFn =
    typeof loggerFn !== 'function'
      ? () => 0
      : (p) => {
          try {
            loggerFn(p);
          } catch (err) {
            console.error(err);
          }
        };

  const unifyFn = async function (...params) {
    let key = '';
    let res: unknown = undefined;
    let doCall = true;
    let err: Error | undefined = undefined;
    try {
      key = serialize(params);
      if (!map[key]) {
        map[key] = {
          resolves: [],
          rejects: [],
          requesting: false,
        };
      }
      res = await new Promise((resolve, reject) => {
        map[key].resolves.push(resolve);
        map[key].rejects.push(reject);
        if (map[key].requesting) {
          doCall = false;

          return;
        }
        map[key].requesting = true;
        const promise = fn(...params);
        if (promise instanceof Promise) {
          promise
            .then((res) => {
              map[key].resolves.forEach((resFn) => resFn(res));
              delete map[key];
            })
            .catch((err) => {
              map[key].rejects.forEach((rejFn) => rejFn(err));
              delete map[key];
            });
          /**
           * 请勿在finally中进行清理，会有潜在问题，参考单元测试用例
           */
          // .finally(() => {
          //   delete map[key];
          // });
        } else {
          err = new Error('unify仅适用于包装异步函数');
          map[key].resolves.forEach((resFn) => resFn(promise));
          delete map[key];
        }
      });
    } catch (error) {
      if (error instanceof SerializeError) {
        res = await fn(...params);
        err = error;
      } else {
        // 调用 fn 出错：
        delete map[key];
        throw error;
      }
    }
    logger({ key, res, doCall, params, err });

    return res;
  };

  return unifyFn as Function as T;
}
